Reusable Sticky Notes Component
A fully-featured, drag-and-drop sticky notes component for React applications with TypeScript support. Features auto-save, rich text editing, customizable colors, and server persistence.
Source Code
✨ Features
- 🎯 Drag & Drop - Move notes anywhere on screen
- 📏 Resizable - Adjust note size with corner/edge handles
- 💾 Auto-save - Automatic debounced saving to backend
- 🎨 Rich Text Editor - Bold, italic, underline, lists
- 🎨 Color Themes - Multiple color options
- 📦 Minimize/Restore - Collapse notes to save space
- 👁️ Hide/Show - Toggle note visibility
- 📱 Responsive - Works on all screen sizes
- 🔌 Backend Agnostic - Customizable API adapter
- ⚡ Zustand State Management - Efficient and performant
- 🎭 Fully Typed - Complete TypeScript support
📦 Installation
Prerequisites
npm install zustand lucide-react
# or
yarn add zustand lucide-react
# or
pnpm add zustand lucide-react
Copy Files
Copy the following files to your project:
reusable-components/sticky-notes/
├── components/
│ ├── StickyNotesManager.tsx
│ └── StickyNoteComponent.tsx
├── store/
│ └── useStickyNotesStore.ts
├── adapters/
│ ├── apiAdapter.ts
│ └── defaultApiAdapter.ts
├── types/
│ └── index.ts
└── hooks/
└── useStickyNotes.ts
🚀 Quick Start
1. Configure API Adapter
Create your API adapter implementing the StickyNotesApiAdapter interface:
// src/adapters/myStickyNotesAdapter.ts
import { StickyNotesApiAdapter, StickyNote } from 'reusable-components/sticky-notes';
export const myStickyNotesAdapter: StickyNotesApiAdapter = {
async fetchNotes() {
const response = await fetch('/api/my-notes');
const data = await response.json();
return data.notes; // Return array of notes
},
async createNote(note: Partial<StickyNote>) {
const response = await fetch('/api/my-notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(note),
});
const data = await response.json();
return data.note;
},
async updateNote(id: string, updates: Partial<StickyNote>) {
const response = await fetch(`/api/my-notes/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
const data = await response.json();
return data.note;
},
async deleteNote(id: string) {
await fetch(`/api/my-notes/${id}`, { method: 'DELETE' });
},
// Optional: Implement getAuthToken if your API requires authentication
getAuthToken: () => localStorage.getItem('token'),
};
2. Initialize Store with Your Adapter
// src/App.tsx or main entry point
import { initializeStickyNotesStore } from 'reusable-components/sticky-notes';
import { myStickyNotesAdapter } from './adapters/myStickyNotesAdapter';
// Initialize once at app startup
initializeStickyNotesStore(myStickyNotesAdapter);
3. Add to Your Application
// src/App.tsx
import { StickyNotesManager } from 'reusable-components/sticky-notes';
function App() {
return (
<div className="app">
{/* Your app content */}
{/* Sticky notes overlay */}
<StickyNotesManager />
</div>
);
}
4. Add Controls (Optional)
Use the provided hook to add controls anywhere:
import { useStickyNotes } from 'reusable-components/sticky-notes';
function MyControls() {
const { addNote, toggleVisibility, notes, isVisible } = useStickyNotes();
return (
<div>
<button onClick={addNote}>Add Note</button>
<button onClick={toggleVisibility}>
{isVisible ? 'Hide' : 'Show'} Notes ({notes.length})
</button>
</div>
);
}
🔧 Configuration
Custom Colors
Edit the colors array in the store:
const colors = [
'#fef3c7', // yellow
'#fde68a', // amber
'#fed7aa', // orange
// Add your custom colors...
];
Auto-save Timing
Adjust debounce times in useStickyNotesStore.ts:
const AUTO_SAVE_DELAY = 3000; // 3 seconds (default)
const NEW_NOTE_SAVE_DELAY = 1500; // 1.5 seconds for new notes
📖 API Reference
StickyNotesApiAdapter Interface
interface StickyNotesApiAdapter {
fetchNotes: () => Promise<StickyNote[]>;
createNote: (note: Partial<StickyNote>) => Promise<StickyNote>;
updateNote: (id: string, updates: Partial<StickyNote>) => Promise<StickyNote>;
deleteNote: (id: string) => Promise<void>;
getAuthToken?: () => string | null;
}
StickyNote Type
interface StickyNote {
id: string;
title: string;
content: string; // Rich HTML content
position: { x: number; y: number };
size: { width: number; height: number };
isMinimized: boolean;
isVisible: boolean;
color: string;
createdAt: string;
updatedAt: string;
isDirty?: boolean; // Has unsaved changes
isSaving?: boolean; // Currently saving
isUnsaved?: boolean; // Never been saved to server
}
useStickyNotes Hook
const {
notes, // Array of all notes
isVisible, // Global visibility state
autoSaveEnabled, // Auto-save toggle state
isLoading, // Loading state
error, // Error message
addNote, // () => void - Create new note
updateNote, // (id, updates) => void
updateTitle, // (id, title) => void
deleteNote, // (id) => Promise<void>
saveNote, // (id) => Promise<void>
saveAllNotes, // () => Promise<void>
toggleVisibility, // () => void - Show/hide all
hideNote, // (id) => void
showNote, // (id) => void
minimizeNote, // (id) => void
restoreNote, // (id) => void
toggleAutoSave, // () => void
loadNotesFromServer,// () => Promise<void>
} = useStickyNotes();
🎨 Styling Requirements
This component uses Tailwind CSS. Ensure your project has:
// tailwind.config.js
module.exports = {
content: [
// ... your content paths
'./reusable-components/**/*.{js,ts,jsx,tsx}',
],
// ... rest of config
}
🔌 Backend API Requirements
Your backend should implement these endpoints:
GET /api/notes - Fetch all notes
POST /api/notes - Create new note
PUT /api/notes/:id - Update note
DELETE /api/notes/:id - Delete note
Response Format Examples:
// GET /api/notes
{
"success": true,
"data": [
{
"id": "note-123",
"title": "My Note",
"content": "<p>Note content with <strong>rich</strong> text</p>",
"position": { "x": 100, "y": 100 },
"size": { "width": 250, "height": 200 },
"isMinimized": false,
"color": "#fef3c7",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z"
}
]
}
// POST /api/notes (Create)
// PUT /api/notes/:id (Update)
{
"success": true,
"data": {
"id": "note-123",
// ... note fields
}
}
🛠️ Advanced Usage
Manual Save Control
const { saveNote, notes } = useStickyNotes();
// Save specific note
await saveNote('note-id');
// Save all dirty notes
await Promise.all(
notes.filter(n => n.isDirty).map(n => saveNote(n.id))
);
Custom Event Handling
import { useStickyNotesStore } from 'reusable-components/sticky-notes';
// Subscribe to store changes
const unsubscribe = useStickyNotesStore.subscribe(
(state) => console.log('Notes updated:', state.notes.length)
);
// Cleanup when done
unsubscribe();
Keyboard Shortcuts
Built-in shortcuts in note content:
Ctrl/Cmd + B- BoldCtrl/Cmd + I- ItalicCtrl/Cmd + U- Underline- Double-click title to edit
EnterorEscapeto finish title edit
🐛 Troubleshooting
Notes disappear after saving
- Check API response format matches expected structure
- Ensure server returns the note with an
idfield - Check browser console for errors
Auto-save not working
- Verify
autoSaveEnabledis true - Check that your API adapter is properly configured
- Look for network errors in browser console
Drag/resize issues
- Ensure parent container doesn't have conflicting event handlers
- Check z-index conflicts with other elements
📄 License
MIT - Feel free to use in your projects!
🤝 Contributing
Contributions welcome! This is a standalone component designed to be easily integrated into any React + TypeScript project.